package com.magenta.maxoptra.geocoding.map24;

import com.magenta.maxoptra.dto.GeoLocationRecord;
import com.magenta.maxoptra.gis.*;
import com.magenta.maxoptra.gis.commons.StringUtils;
import com.magenta.mrs.basegeocoding.BaseGeocoding;
import com.magenta.mrs.util.GeoInformationClient;
import com.mapsolute.map24webservices15.clients.axis.stubs.*;
import com.mapsolute.map24webservices15.clients.axis.wrappers.Map24Geocoder51Wrapper;
import com.mapsolute.map24webservices15.clients.axis.wrappers.Map24RoutingServiceWrapper;

import java.util.ArrayList;
import java.util.List;
/*
* Project: Maxoptra
* Author: Kirill Sychin
* Date: 23.10.2008 15:10:24
*
* Copyright (c) 1999-2008 Magenta Corporation Ltd. All Rights Reserved.
* Magenta Technology proprietary and confidential.
* Use is subject to license terms.
*/

public class GeocoderMap24Impl extends BaseGeocoding<Float> implements IGeocoding, IGeoRouting {

    public static final String MAP24_SERVER_SOAP = "MAP24_SERVER_SOAP";
    public static final String MAP24_ID = "MAP24_ID";
    public static final String MAP24_CLIENT_ID = "MAP24_CLIENT_ID";

    public String getProviderName() {
        return MapProviderType.map24.name();
    }

    public boolean accuracyIsAccurateEnough(String accuracyThreshold, Float accuracy) {
        Float accuracyThresholdParsed = Float.valueOf(accuracyThreshold);
        return accuracy >= accuracyThresholdParsed;
    }

    private static GeographicCoordinates toCoords(final GeoAddress addr) {
        return new GeographicCoordinates() {
            public double getLatitude() {
                return addr.getLatitude();
            }

            public double getLongitude() {
                return addr.getLongitude();
            }

            public int getLatitudeInteger() {
                return RoutingCoordinate.getIntCoord(getLatitude());
            }

            public int getLongitudeInteger() {
                return RoutingCoordinate.getIntCoord(getLongitude());
            }


        };
    }

    public static void main(String[] args) {
        IGeocoding iGeocoding = new GeocoderMap24Impl();
        IGeoRouting iGeorouting = new GeocoderMap24Impl();
        GeoProviderSettings settings = new GeoProviderSettings(MapProviderType.map24.name(), "1.0", null);
        try {
            //iGeocoding.findAddressByKeyword("RU", "Moscow", 5);
            //List<GeoAddress> ls = iGeocoding.findAddressByKeyword("GB", "London", 7);
            //List<GeoAddress> gs = iGeocoding.findAddressByKeyword("GB", "Greenwich", 7);
            //List<GeoAddress> ss = iGeocoding.findAddressByKeyword("GB", "Seltic", 7);
            List<GeoAddress> froms = iGeocoding.findAddressByKeyword(settings, "TA8 1GA", 7);
            List<GeoAddress> tos = iGeocoding.findAddressByKeyword(settings, "TA9 3BD", 7);
            GeoRoute route = iGeorouting.getRoute(toCoords(froms.get(0)), toCoords(tos.get(0)));
            System.out.println(route);
        } catch (GeoException e) {
            e.printStackTrace();
        } catch (ServerUnavailableException e) {
            e.printStackTrace();
        }
    }

    protected List<GeoAddress> doFindAddressByKeyword(GeoProviderSettings settings, String keyword, int i, List<Float> accuracies) throws GeoException {
        List<GeoAddress> list = new ArrayList<GeoAddress>();
        Map24Parameters gisParams = getMap24Parameters();
        final MapSearchResponse response = Map24Geocoder51Wrapper.SearchFree(gisParams.getServerUrl(), gisParams.getMapId(), gisParams.getClientId(), keyword, i);
        if (response != null) {
            for (MapSearchAlternative geocodedAddress : response.getAlternatives()) {
                final Property[] propertiesAddress = geocodedAddress.getPropertiesMajor();
                GeoAddress geoAddress = new GeoAddress();
                for (Property property : propertiesAddress) {
                    if (property.getKey().equals(IResponseAlternative.CITY)) {
                        geoAddress.setCity(property.getValue());
                    } else if (property.getKey().equals(IResponseAlternative.COUNTRY) || property.getKey().equals(IResponseAlternative.CTRY)) {
                        geoAddress.setCountry(property.getValue());
                    } else if (property.getKey().equals(IResponseAlternative.STATE) || property.getKey().equals(IResponseAlternative.STA)) {
                        geoAddress.setState(property.getValue());
                    } else if (property.getKey().equals(IResponseAlternative.STREET)) {
                        geoAddress.setStreet(property.getValue());
                    } else if (property.getKey().equals(IResponseAlternative.ZIP)) {
                        geoAddress.setZip(property.getValue());
                    } else if (property.getKey().equals(IResponseAlternative.NAME) || property.getKey().equals(IResponseAlternative.BRAND)) {
                        geoAddress.setInfo(property.getValue());
                    }
                }
                geoAddress.setLatitude(geocodedAddress.getCoordinate().getLatitude() / 60);
                geoAddress.setLongitude(geocodedAddress.getCoordinate().getLongitude() / 60);
                if (accuracies != null) {
                    accuracies.add(geocodedAddress.getSimilarity());
                }
                list.add(geoAddress);
            }
        } else {
            throw new GeoException("Webservice is inactive. Check that Map24 key is not expired.");
        }
        return list;
    }

    private static Coordinate newCoordinate(GeographicCoordinates coords) {
        return Map24RoutingServiceWrapper.Coordinate(coords.getLongitude(), coords.getLatitude());
    }

    //TODO ivictus: refactoring select same codes from both methods below.
    public GeoRoute findShortestRoute(GeographicCoordinates from, GeographicCoordinates to) throws GeoException {

        GeoRoute route = null;

        Map24Parameters gisParams = getMap24Parameters();
        final CalculateRouteRequest request =
                Map24RoutingServiceWrapper.CalculateRouteRequest(newCoordinate(from), newCoordinate(to));
        request.setCalculationMode(RouteCalculationMode.Shortest);
        request.setVehicleType(RoutingRequestVehicleType.Car);
        request.setMinQualityOfGeocodeResults(GeocodedAddressQuality.ZIP);
        final CalculateRouteResponse response =
                Map24RoutingServiceWrapper.CalculateTimeDistance(gisParams.getServerUrl(), gisParams.getMapId(), gisParams.getClientId(), request);
        if (response != null) {
            Route r = response.getRoute();
            //FIXME ivictus: add convertation from provider time unit to millis
            route = new GeoRoute(GeoPrecision.convertFromMetersToMiles(r.getTotalLength()),
                    r.getTotalTime(), from, to);
        } else {
            throw new GeoException("Webservice is inactive");
        }
        return route;
    }

    class Map24Parameters {
        private String serverUrl = null;
        private String mapId = null;
        private String clientId = null;

        public Map24Parameters(String serverUrl, String mapId, String clientId) {
            this.clientId = clientId;
            this.mapId = mapId;
            this.serverUrl = serverUrl;
        }

        public String getServerUrl() {
            return serverUrl;
        }

        public String getMapId() {
            return mapId;
        }

        public String getClientId() {
            return clientId;
        }
    }


    private Map24Parameters getMap24Parameters() throws GeoException{
        GisParameters gisParams = getProviderParams();
        String serverUrl;
        String mapId;
        String clientId;
        if (gisParams!= null) {
            serverUrl = gisParams.get(GisParameter.base_url.withProvider(MapProviderType.map24.name()));
            if (StringUtils.isEmpty(serverUrl)) {
                throw new GeoException("Parameter "+GisParameter.base_url +" is not set for "+MapProviderType.map24.name());
            }
            mapId = gisParams.get(GisParameter.map_id.withProvider(MapProviderType.map24.name()));
            if (StringUtils.isEmpty(mapId)) {
                throw new GeoException("Parameter "+GisParameter.map_id +" is not set for "+MapProviderType.map24.name());
            }
            clientId = gisParams.get(GisParameter.map_key.withProvider(MapProviderType.map24.name()));
            if (StringUtils.isEmpty(clientId)) {
                throw new GeoException("Parameter "+GisParameter.map_key +" is not set for "+MapProviderType.map24.name());
            }
        } else {
            throw new GeoException("Gis parameters is not set for "+MapProviderType.map24.name());
        }
        return new Map24Parameters(serverUrl, mapId, clientId);
    }


    public GeoRoute findFastestRoute(GeographicCoordinates from, GeographicCoordinates to) throws GeoException {

        GeoRoute route = null;

        Map24Parameters gisParams = getMap24Parameters();

        final CalculateRouteRequest request =
                Map24RoutingServiceWrapper.CalculateRouteRequest(newCoordinate(from), newCoordinate(to));
        request.setCalculationMode(RouteCalculationMode.Fastest);
        request.setVehicleType(RoutingRequestVehicleType.Car);
        request.setMinQualityOfGeocodeResults(GeocodedAddressQuality.ZIP);
        final CalculateRouteResponse response =
                Map24RoutingServiceWrapper.CalculateTimeDistance(gisParams.getServerUrl(), gisParams.getMapId(), gisParams.getClientId(), request);
        if (response != null) {
            Route r = response.getRoute();
            //FIXME ivictus: add convertation from provider time unit to millis
            route = new GeoRoute(GeoPrecision.convertFromMetersToMiles(r.getTotalLength()),
                    r.getTotalTime(), from, to);
        } else {
            throw new GeoException("Webservice is inactive");
        }
        return route;
    }

    public GeoRoute getRoute(GeographicCoordinates from, GeographicCoordinates to, VehicleType vehicleType) throws GeoException {
        throw new UnsupportedOperationException("Method getRoute of class com.magenta.maxoptra.geocoding.map24.GeocoderMap24Impl is unimplemented!");
    }

    @Override
    public List<GeoLocationRecord> getRoutePoints(GeoProviderSettings settings, GeographicCoordinates from, GeographicCoordinates to) throws GeoException {
        throw new UnsupportedOperationException();
    }

    public void setClient(GeoInformationClient client) {
        this.client = client;
    }

    @Override
    public List<GeoAddress> doForwardGeocoding(GeoProviderSettings settings, GeoAddress address, int resultAddressLimit)
            throws GeoException {
        throw new UnsupportedOperationException("");
    }

    @Override
    public GeoRoute getRoute(List<GeographicCoordinates> coordinates, VehicleType vehicleType) throws GeoException {
        throw new UnsupportedOperationException("getRoute by list of coordinates is not supported for GeocoderMap24");
    }

    @Override
    public List<GeoRoute> getRoute(GeographicCoordinates from, List<? extends GeographicCoordinates> to, VehicleType vehicleType,int timeout) throws GeoException {
        throw new UnsupportedOperationException("getRoute from coordinate to list of coordinates is not supported for GeocoderMap24");
    }

    public synchronized List<GeoLocationRecord> getRoutePoints(GeoProviderSettings settings, List<GeographicCoordinates> coordinates) throws GeoException {
        throw new UnsupportedOperationException("getRoutePoints by list of coordinates is not supported for GeocoderMap24");
    }

    public GeoAddress findCountry(String country, String keyword) throws GeoException {
        throw new UnsupportedOperationException("IGeocoding.findCountry is not implemented for GeocoderMap24");
    }

    @Override
    public List<GeoRoute> getRoutes(GeographicCoordinates from, List<? extends GeographicCoordinates> to, VehicleType vehicleType, int timeout) throws GeoException {
        throw new UnsupportedOperationException("getRoutes from coordinate to list of coordinates is not supported for GeocoderMap24");
    }

    @Override
    public List<GeoRoute> getCoordinateToCoordinateRoutes(List<GeographicCoordinates> coordinates) throws ServerUnavailableException {
        throw new UnsupportedOperationException("getCoordinateToCoordinateRoutes from coordinate to list of coordinates is not supported for " + getProviderName());
    }

    @Override
    public List<GeoRoute> getRoutes(GeographicCoordinates from, List<? extends GeographicCoordinates> to, int timeout) throws GeoException, ServerUnavailableException {
        return getRoutes(from, to, timeout);
    }

    @Override
    public List<GeoRoute> getRoute(GeographicCoordinates from, List<? extends GeographicCoordinates> to, int timeout) throws GeoException {
        return getRoute(from, to, null, timeout);
    }

    @Override
    public GeoRoute getRoute(List<GeographicCoordinates> coordinates) throws GeoException {
        return getRoute(coordinates, null);
    }

    @Override
    public synchronized GeoRoute getRoute(GeographicCoordinates from, GeographicCoordinates to) throws GeoException, ServerUnavailableException {
        return getRoute(from, to, null);
    }

}
